﻿/*########################################################################
    Filename:     MenuWndHook.cpp
    ----------------------------------------------------
    Remarks:    ...
    ----------------------------------------------------
    Author:        成真
    Email:        anyou@sina.com
                anyou@msn.com
    Created:    7/4/2003 17:38
                6/20/2012 黄建雄，修改为使用SSkin皮肤绘制非客户区边框，支持边框大小配置
  ########################################################################*/

#include "souistd.h"
#include "helper/SMenuWndHook.h"
#include "res.mgr/Sskinpool.h"
#include "core/Sskin.h"

#ifdef _WIN64
    #define GWL_WNDPROC    GWLP_WNDPROC
#endif//_WIN64

namespace SOUI {

    const TCHAR CoolMenu_oldProc[] = _T("CoolMenu_oldProc");
    
    
#define SM_CXMENUBORDER    3        //默认菜单边框大小
    
    
    /*########################################################################
                  ------------------------------------------------
                                    class CMenuWndHook
                  ------------------------------------------------
      ########################################################################*/
    SMap <HWND, SMenuWndHook *> SMenuWndHook::m_WndMenuMap;
    
    HHOOK SMenuWndHook::m_hMenuHook = NULL;
    SStringW SMenuWndHook::m_strSkinName;
    
    SMenuWndHook::SMenuWndHook (HWND hWnd)
        : m_hWnd(hWnd) {
        
    }
    
    SMenuWndHook::~SMenuWndHook () {
        WNDPROC oldWndProc = (WNDPROC)::GetProp(m_hWnd, CoolMenu_oldProc);
        if (oldWndProc != NULL) {
            ::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc);
            ::RemoveProp(m_hWnd, CoolMenu_oldProc);
        }
        m_WndMenuMap.RemoveKey(m_hWnd);
    }
    
    void SMenuWndHook::InstallHook(HINSTANCE hInst, LPCWSTR pszSkinName) {
        if (m_hMenuHook == NULL ) {
            m_hMenuHook = ::SetWindowsHookEx(WH_CALLWNDPROC, WindowHook,
                                             hInst, ::GetCurrentThreadId());
            m_strSkinName = pszSkinName;
        }
    }
    
    void SMenuWndHook::UnInstallHook() {
        SPOSITION pos = m_WndMenuMap.GetStartPosition();
        while(pos) {
            SMap<HWND, SMenuWndHook *>::CPair *p = m_WndMenuMap.GetNext(pos);
            delete p->m_value;
        }
        m_WndMenuMap.RemoveAll();
        
        if (m_hMenuHook != NULL) {
            ::UnhookWindowsHookEx(m_hMenuHook);
        }
    }
    
    SMenuWndHook *SMenuWndHook::GetWndHook(HWND hwnd) {
    
        SMap<HWND, SMenuWndHook *>::CPair *p = m_WndMenuMap.Lookup(hwnd);
        if(!p) {
            return NULL;
        }
        return p->m_value;
    }
    
    SMenuWndHook *SMenuWndHook::AddWndHook(HWND hwnd) {
        SMenuWndHook *pWnd = GetWndHook(hwnd);
        if(pWnd) {
            return pWnd;
        }
        
        pWnd = new SMenuWndHook(hwnd);
        if (pWnd != NULL) {
            m_WndMenuMap[hwnd] = pWnd;
        }
        return pWnd;
    }
    
    /*########################################################################
                  ------------------------------------------------
                                      消息过程
                  ------------------------------------------------
      ########################################################################*/
    LRESULT CALLBACK SMenuWndHook::WindowHook(int code, WPARAM wParam, LPARAM lParam) {
        CWPSTRUCT *pStruct = (CWPSTRUCT *)lParam;
        
        while (code == HC_ACTION) {
            HWND hWnd = pStruct->hwnd;
            
            if (pStruct->message != WM_CREATE && pStruct->message != 0x01E2) {
                break;
            }
            
            // 是否为菜单类 ----------------------------------------
            TCHAR strClassName[10];
            int Count = ::GetClassName (hWnd, strClassName, sizeof(strClassName) / sizeof(strClassName[0]));
            if (Count != 6 || _tcscmp(strClassName, _T("#32768")) != 0 ) {
                break;
            }
            
            // 是否已经被子类化 ------------------------------------
            if (::GetProp(hWnd, CoolMenu_oldProc) != NULL ) {
                break;
            }
            
            AddWndHook(pStruct->hwnd);
            
            // 取得原来的窗口过程 ----------------------------------
            WNDPROC oldWndProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
            if (oldWndProc == NULL) {
                break;
            }
            
            SASSERT(oldWndProc != CoolMenuProc);
            // 保存到窗口的属性中 ----------------------------------
            if (!SetProp(hWnd, CoolMenu_oldProc, oldWndProc) ) {
                break;
            }
            
            // 子类化 ----------------------------------------------
            if (!SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)CoolMenuProc) ) {
                ::RemoveProp(hWnd, CoolMenu_oldProc);
                break;
            }
            
            break;
        }
        return CallNextHookEx (m_hMenuHook, code, wParam, lParam);
    }
    
    LRESULT CALLBACK SMenuWndHook::CoolMenuProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
        WNDPROC oldWndProc = (WNDPROC)::GetProp(hWnd, CoolMenu_oldProc);
        SMenuWndHook *pWnd = NULL;
        
        switch (uMsg) {
            case WM_CREATE: {
                LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    lResult = (LRESULT)pWnd->OnCreate((LPCREATESTRUCT)lParam);
                }
                return lResult;
            }
            break;
            case WM_NCCALCSIZE: {
                LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    pWnd->OnNcCalcsize((BOOL)wParam, (NCCALCSIZE_PARAMS *)lParam);
                }
                return lResult;
            }
            break;
            case WM_WINDOWPOSCHANGING: {
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    pWnd->OnWindowPosChanging((LPWINDOWPOS)lParam);
                }
            }
            break;
            case WM_WINDOWPOSCHANGED: {
                LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    pWnd->OnWindowPosChanged();
                }
                return lResult;
            }
            break;
            case WM_PRINT: {
                LRESULT lResult = CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    pWnd->OnPrint((HDC)wParam);
                }
                return lResult;
            }
            break;
            case WM_NCPAINT: {
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    pWnd->OnNcPaint();
                    return 0;
                }
            }
            break;
            case WM_NCDESTROY: {
                if ((pWnd = GetWndHook(hWnd)) != NULL) {
                    pWnd->OnNcDestroy();
                }
            }
            break;
        }
        return CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
    }
    
    /*########################################################################
                  ------------------------------------------------
                                    消息处理函数
                  ------------------------------------------------
      ########################################################################*/
    int SMenuWndHook::OnCreate(LPCREATESTRUCT lpCreateStruct) {
        SetClassLong(m_hWnd, GCL_STYLE, GetClassLong(m_hWnd, GCL_STYLE) & ~CS_DROPSHADOW);
        return 0;
    }
    
    void SMenuWndHook::OnWindowPosChanging(WINDOWPOS *pWindowPos) {
        if(m_strSkinName.IsEmpty()) {
            return;
        }
        ISkinObj *pSkin = GETSKIN(m_strSkinName, 100);
        if(!pSkin || !pSkin->IsClass(SSkinImgFrame::GetClassName())) {
            return;
        }
        SSkinImgFrame *pBorderSkin = static_cast<SSkinImgFrame *>(pSkin);
        if(!pBorderSkin) {
            return;
        }
        pWindowPos->cx += pBorderSkin->GetMargin().left + pBorderSkin->GetMargin().right - SM_CXMENUBORDER * 2;
        pWindowPos->cy += pBorderSkin->GetMargin().top + pBorderSkin->GetMargin().bottom - SM_CXMENUBORDER * 2;
    }
    
    void SMenuWndHook::OnNcCalcsize(BOOL bValidCalc, NCCALCSIZE_PARAMS *lpncsp) {
        if(m_strSkinName.IsEmpty()) {
            return;
        }
        ISkinObj *pSkin = GETSKIN(m_strSkinName, 100);
        if(!pSkin || !pSkin->IsClass(SSkinImgFrame::GetClassName())) {
            return;
        }
        SSkinImgFrame *pBorderSkin = static_cast<SSkinImgFrame *>(pSkin);
        if(!pBorderSkin) {
            return;
        }
        
        lpncsp->rgrc[0].left = lpncsp->lppos->x + pBorderSkin->GetMargin().left;
        lpncsp->rgrc[0].top = lpncsp->lppos->y + pBorderSkin->GetMargin().top;
        lpncsp->rgrc[0].right = lpncsp->lppos->x + lpncsp->lppos->cx - pBorderSkin->GetMargin().right;
        lpncsp->rgrc[0].bottom = lpncsp->lppos->y + lpncsp->lppos->cy - pBorderSkin->GetMargin().bottom;
    }
    
    void SMenuWndHook::OnNcPaint() {
        HDC dc = GetWindowDC(m_hWnd);
        OnPrint(dc);
        ReleaseDC(m_hWnd, dc);
    }
    
    void SMenuWndHook::OnPrint(HDC dc) {
        if(m_strSkinName.IsEmpty()) {
            return;
        }
        ISkinObj *pSkin = GETSKIN(m_strSkinName, 100);
        if(!pSkin) {
            return;
        }
        SSkinImgList *pBorderSkin = static_cast<SSkinImgList *>(pSkin);
        if(!pBorderSkin) {
            return;
        }
        
        CRect rcClient;
        GetClientRect(m_hWnd, &rcClient);
        ClientToScreen(m_hWnd, (LPPOINT)&rcClient);
        ClientToScreen(m_hWnd, ((LPPOINT)&rcClient) + 1);
        CRect rcWnd;
        GetWindowRect(m_hWnd, &rcWnd);
        rcClient.OffsetRect(-rcWnd.TopLeft());
        
        int nSave = ::SaveDC(dc);
        ::ExcludeClipRect(dc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
        rcWnd.MoveToXY(0, 0);
        
        SAutoRefPtr<IRenderTarget> pRT;
        GETRENDERFACTORY->CreateRenderTarget(&pRT, rcWnd.Width(), rcWnd.Height());
        pBorderSkin->DrawByIndex(pRT, rcWnd, 0);
        HDC hmemdc = pRT->GetDC(0);
        ::BitBlt(dc, 0, 0, rcWnd.Width(), rcWnd.Height(), hmemdc, 0, 0, SRCCOPY);
        pRT->ReleaseDC(hmemdc);
        ::RestoreDC(dc, nSave);
    }
    
    void SMenuWndHook::OnNcDestroy() {
        delete this;
    }
    
//不能设计窗口半透明，设置区域后，非客户区位置发生改变，不明白原因。
    void SMenuWndHook::OnWindowPosChanged() {
        /*
            CRect rcWnd;
            GetWindowRect(m_hWnd,&rcWnd);
            rcWnd.MoveToXY(0,0);
            HRGN hRgn = ::CreateEllipticRgnIndirect(&rcWnd);
            SetWindowRgn(m_hWnd,hRgn,TRUE);
            DeleteObject(hRgn);
            */
    }
    
}//namespace SOUI